// scaup - Scala up!
// (c) 2008-2009, Normen Müller, normen.mueller@gmail.com  
// $Id: GRS.scala 2482 2009-10-07 16:55:33Z nmueller $
package locutor.model.parsing.grgen

import java.io.File
import java.text.MessageFormat.{format}

import scaup.Views._

/** 
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.5 */
case class GRSError(message: String) extends Exception(message)

/** This object provides a simple interface to the GRS parser class.
 * 
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.5 */
object GRS extends GRSParser {
  def apply(input: File): Any =
    if(input == null || !(input exists))
      throw new GRSError(format("Physical GRS file may not be null (''{0}'').", input))
    else apply(input.content)
  
  def apply(input: String) =
    if(input == null)
      throw new GRSError("Physical GRS representation may not be null.")
    else parse(input)
  
  def parse(input: String) = phrase(grs)(new lexical.Scanner(input)) match {
    case Success(result, _) => result
    case n@_                => throw new GRSError(format("GRS parsing error in ''{0} ...}''\n", input.lines.next) + n)
  }
}

import scala.util.parsing.combinator._
import scala.util.parsing.combinator.lexical._
import scala.util.parsing.input.CharArrayReader.EofCh

/** 
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.5 */
private [grgen] class GRSLexer extends StdLexical with RegexParsers {
  override type Elem = Char

  override def token = 
    ( string                             ^^ StringLit
    | number                             ^^ NumericLit
    | delim
    | EofCh                             ^^^ EOF
    | id                                 ^^ {case i if reserved contains i => Keyword(i) case i => Identifier(i)}        
    | '\"' ~> failure("Unterminated string")
    | failure("Illegal character")
    )
  
  override def whitespace = rep(whitespaceChar | "\\" | '#' ~ rep(chrExcept(EofCh, '\n')))
  
  def string  =      
    ( '\"' ~> rep(charSeq | chrExcept('\"', '\n', EofCh)) <~ '\"' ^^ { _ mkString "" }
    | '\'' ~> rep(charSeq | chrExcept('\'', '\n', EofCh)) <~ '\'' ^^ { _ mkString "" }    
    )
  
  def number  = """-?(\d+(\.\d*)?|\d*\.\d+)([eE][+-]?\d+)?[fFdD]?""".r
  
  def id      = rep(letter | digit | elem("underscore", _=='_')) ^^ { _ mkString "" }
  
  def charSeq = 
    ( '\\' ~ '\"' ^^^ "\"" | '\\' ~ '\\' ^^^ "\\" | '\\' ~ '/'  ^^^ "/" | '\\' ~ 'b'  ^^^ "\b" | '\\' ~ 'f'  ^^^ "\f" 
    | '\\' ~ 'n'  ^^^ "\n" |'\\' ~ 'r'  ^^^ "\r" |'\\' ~ 't'  ^^^ "\t" |'\\' ~ 'u' ~> unicodeBlock
    )
  
  import scala.io.UTF8Codec.{encode}
  def unicodeBlock = hexDigit ~ hexDigit ~ hexDigit ~ hexDigit ^^ {
    case a ~ b ~ c ~ d => new String(encode(Integer.parseInt(List(a, b, c, d) mkString "", 16)))
  }
  
  val hexDigits = Set[Char]() ++ "0123456789abcdefABCDEF".toArray
  
  def hexDigit = elem("hex digit", hexDigits.contains(_))
}

/** 
 * Restrictions:
 * - Command line parsing (!<shell command>) not yet supported
 * 
 * @thanks GrShell.csc and GrGEN.NET manual for syntax rules (v2.5.x)
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.5 */
private [grgen] class GRSParser extends XGRSParser {
  /* Fill in abstract defs*/
  type Tokens = GRSLexer
  val lexical: Tokens = new GRSLexer
  
  /* Configure lexical parsing */                                 
  lexical.reserved ++= List(
    "actions", "add", "allocvisitflag", "apply", "attributes", "any",
    "backend", "bordercolor", "by",
    "clear", "color", "custom",
    "debug", "delete", "disable", "dump",
    "echo", "edge", "edges", "emit", "enable", "exclude", "export", "exit",
    "file", "freevisitflag",
    "get", "graph", "graphs", "group",
    "help", "hidden",
    "import", "include", "incoming", "infotag", "io", "is", "isvisited",
    "labels", "layout",
    "map",
    "new", "no", "node", "nodes", "null", "num",
    "off", "on", "only", "open", "option", "options", "outgoing",
    "parse", "parser",
    "quit",
    "randomseed", "redirect", "remove", "reset", "resetvisitflag",
    "save", "select", "set", "setvisited", "size", "shape", "shortinfotag", "show", "silence", "strict", "sub",
    "super", "sync",
    "textcolor", "type", "types",
    "validate", "var",
    "with",             
    "xgrs"      
  ) ++ XGRSParser.reserved
  
  lexical.delimiters ++= List(
    "{", "}", "[", "]",
    "@", ";;", 
    ";", ".", "/", "->", "-"
  ) ++ XGRSParser.delimiters
  
  /* Define the grammar */
  def grs = 
    (( "help" ~ spaced_params
    | "quit" | "exit"
    | "include" ~ filename
    | "new" ~ new_command
    | "open" ~ "graph" ~ filename ~ text
    | "select" ~ select_command
    | "silence" ~ ("on" | "off")
    | "delete" ~ delete_command
    | "clear" ~ "graph" ~ opt(graph)
    | "show" ~ show_command
    | "node" ~ "type" ~ node ~ "is" ~ node
    | "edge" ~ "type" ~ edge ~ "is" ~ edge
    | "debug" ~ debug_command
    | "xgrs" ~ xgrs
    | "validate" ~ (("xgrs" ~ xgrs) | opt("strict"))
    | "dump" ~ dump_command
    | "save" ~ "graph" ~ filename
    | "export" ~ filename
    | "import" ~ ((filename)+)
    | "echo" ~ text
    | "custom" ~ custom_command
    | "redirect" ~ "emit" ~ filename
    | "sync" ~ "io"
    | "parse" ~ (("file" ~ filename) | text)
    | "randomseed" ~ (numeric | text)
    | "isvisited" ~ graph_elem ~ (numeric | text)
    | "setvisited" ~ graph_elem ~ (numeric | text) ~ ("true" | "false" | text)
    | "freevisitflag" ~ (numeric | text)
    | "resetvisitflag" ~ (numeric | text)
    | "map" ~ map_command
    | "set" ~ set_command
    | opt(graph_elem ~ ".") ~ ident ~
        opt("=" ~ ( "allocvisitflag" | text | numeric
                  | ("isvisited" ~ graph_elem ~ (numeric | text))
                  | "new" ~ (("map" ~ text ~ text) | ("set" ~ text))
                  | graph_expr
        ))
    )*)  
  
  def new_command = (new_graph | new_edge | new_node)
  def new_graph   = "graph" ~ filename ~ opt(text)
  def new_node    = elem_def  
  def new_edge    = node ~ "-" ~ elem_def ~ "->" ~ node  
  
  def select_command = (select_backend | select_graph | select_actions | select_parser)
  def select_backend = "backend" ~ filename ~ opt(":" ~ params)
  def select_graph   = "graph" ~ graph
  def select_actions = "actions" ~ filename
  def select_parser  = "parser" ~ filename ~ text
  
  def delete_command = (delete_node | delete_edge | delete_graph)
  def delete_node    = "node" ~ node
  def delete_edge    = "edge" ~ edge
  def delete_graph   = "graph" ~ opt(graph)
  
  def show_command = (show_elems | show_types | show_attrs | show_var | show_graph | show_list)
  def show_elems   = opt("num") ~ ("nodes" | "edges") ~ opt(opt("only") ~ text)
  def show_types   = ("node" | "edge") ~ opt("super" | "sub") ~ "types" ~ opt(ident)
  def show_attrs   = ("node" | "edge") ~ (("attributes" ~ opt(opt("only") ~ ident)) | node)
  def show_var     = "var" ~ ident
  def show_graph   = "graph" ~ filename ~ opt(text)
  def show_list    = ("graphs" | "actions" | "backend")
//  def show_elem  = graph_elem ~ "." ~ ident  
  
  def debug_command    =
    ( debug_apply
    | debug_xgrs
    | "enable" | "disable"
    | debug_do_layout | debug_set_layout | debug_get_layout_ops
    )
  def debug_apply          = "apply" ~ opt("(" ~ params ~ ")" ~ "=") ~ text ~ opt("(" ~ rule_params ~ ")") //XXX
  def debug_xgrs           = "xgrs" ~ xgrs
  def debug_do_layout      = "layout"
  def debug_set_layout     = "set" ~ "layout" ~ (("option" ~ text ~ text) | opt(text))
  def debug_get_layout_ops = "get" ~ "layout" ~ "options"

  def dump_command    = (dump_graph | dump_set | dump_add | dump_reset)
  def dump_graph      = "graph" ~ filename
  def dump_set        = "set" ~ (dump_set_node | dump_set_edge | dump_set_labels)
  def dump_add        = "add" ~ (dump_add_node | dump_add_edge)
  def dump_reset      = "reset"
  def dump_set_node   = "node" ~ opt("only") ~ ident ~ ("color" | "bordercolor" | "shape" | "textcolor") ~ text
  def dump_set_edge   = "edge" ~ opt("only") ~ ident ~ ("color" | "textcolor") ~ text
  def dump_set_labels = ("node" | "edge") ~ opt("only") ~ ident ~ "labels" ~ ("on" | "off" | text)
  def dump_add_node   = "node" ~ opt("only") ~ ident ~
    ( "exclude"
    | "group" ~ opt("by" ~ opt("hidden") ~ ("no" | "incoming" | "outgoing" | "any") ~
                      opt(opt("only") ~ ident ~ opt("with" ~ opt("only") ~ ident)))
    | ("infotag" | "shortinfotag") ~ text
    )
  def dump_add_edge   = "edge" ~ opt("only") ~ ident ~
    ( "exclude"
    | ("infotag" | "shortinfotag") ~ text
    )
  
  def custom_command = (custom_graph | custom_actions)
  def custom_graph   = "graph" ~ opt(spaced_params)
  def custom_actions = "actions" ~ opt(spaced_params)  
  
  def map_command = ((graph_elem ~ "." ~ text) | text) ~
    ( ("add" ~ graph_expr ~ graph_expr)
    | ("remove" ~ graph_expr)
    | "size"
    )
  
  def set_command = ((graph_elem ~ "." ~ text) | text) ~
    ( ("add" ~ graph_expr)
    | ("remove" ~ graph_expr)
    | "size"
    )
  
  def graph_elem      = (("@" ~ "(" ~ text ~ ")") | text | "null")
  
  def node            = (("@" ~ "(" ~ text ~ ")") | text)
  
  def edge            = (("@" ~ "(" ~ text ~ ")") | text)
  
  def graph           = (numeric | text)  
  def graph_expr      = ("null" | (graph_elem ~ opt(opt(".") ~ text)) | numeric | "true" | "false")  
  
  def params          = text ~ (("," ~ text)*)
  def rule_params     = (text | ("?" ~ (("," ~ (text | "?"))*)))
  def spaced_params   = rep(text)
  
  def filename        = (string | opt("/") ~ ident ~ rep(("." | "/" | "-") ~ ident))
  
  def elem_def        = opt(text) ~ opt(":" ~ text ~ opt(constructor))
  
  def constructor     = "(" ~ opt(("$" ~ "=" ~ text ~ opt("," ~ attributes)) | attributes) ~ ")"
  
  def attributes: Parser[Any] = (ident ~ "=" ~ (text | numeric)) ~ (("," ~ attributes)*)
//  def assignment_commands = opt(graph_elem ~ ".") ~ ident ~ "=" ~ ("allocvisitflag" | text | numeric)
}
